﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.IO;
using System.Text.RegularExpressions;
using Tag = System.Tuple<string, string>;
using System.Linq;
using System.Xml;
using System.Reflection;

namespace BetterHtml
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            input.Text = @"html
{
  head
    title ""Better HTML Example""
  body
  {
    h1 ""Better HTML""
    p ""This is an example of what Better HTML can do.""
    table {
      tr {
        td @var1
        td @var2|upper
      }
    }
  }
}";
        }

        private static readonly Dictionary<string, Tuple<string, string>> Dict =
            new Dictionary<string, Tuple<string, string>>
                {
                    {"doctype", new Tag("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">", null)},
                    {"a", new Tag("<a>", "</a>")},
                    {"abbr", new Tag("<abbr>", "</abbr>")},
                    {"address", new Tag("<address>", "</address>")},
                    // ...
                    {"table", new Tag("<table>", "</table>")},
                    {"tr", new Tag("<tr>", "</tr>")},
                    {"td", new Tag("<td>", "</td>")},
                    {"p", new Tag("<p>", "</p>")},
                    {"br", new Tag("<br/>", null)},
                    {"hr", new Tag("<hr/>", null)},
                    {"html", new Tag("<html>", "</html>")},
                    {"head", new Tag("<head>", "</head>")},
                    {"title", new Tag("<title>", "</title>")},
                    {"body", new Tag("<body>", "</body>")},
                };


        private static readonly string[] _selfClosingTags = { "area", "base", "basefont", "br", "col", "frame", "hr", "img", "input", "link", "meta", "param" };

        private static readonly Dictionary<string, string> _contextVars = new Dictionary<string,string> {
                { "one", "1"},
                { "two", "2"},
                { "three", "3"},
                { "var", "a variable"},
                { "andvars", "and variables"},
                { "var1", "FirstVariable"},
                { "var2", "SecondVariable"},
            };
        
        enum State
        {
            Tag, Attribute, Value
        }

        private static string SafeAttribute(string attr)
        {
            return Regex.Replace(attr.Replace("'", ""), @"\W+", "_");
        }

        private void input_TextChanged(object sender, TextChangedEventArgs e)
        {
            string lastTag = null;
            var stack = new Stack<int>();
            int openTags = 0;
            int openAttrs = 0;
            State state = State.Tag;

            using (var sw = new StringWriter())
            {
                using (var xw = new XmlTextWriter(sw))
                {
                    xw.WriteDocType("html", "-//W3C//DTD XHTML 1.1//EN", "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd", null);
                    xw.Formatting = Formatting.Indented;
                    xw.Indentation = 2;

                    for (var match = Regex.Match(input.Text, @"\w+|@\w+(?:\|\w+)*|[;{}()=]|(?<q>[""']{3}|[""']).*?(?<!\\)\k<q>|//.*?$|/\*.*?\*/", RegexOptions.Multiline | RegexOptions.Singleline); match.Success; match = match.NextMatch())
                    {
                        string token = match.Value;

                        if (token[0] != '(' && _selfClosingTags.Contains(lastTag) && state == State.Tag)
                            for (; openTags > 0; --openTags) xw.WriteEndElement();

                        switch (token[0])
                        {
                            case '@':
                                var parts = token.Substring(1).Split('|');
                                string varName = parts[0];
                                var filters = parts.Skip(1).ToArray();
                                string value;

                                if (_contextVars.ContainsKey(varName))
                                {
                                    value = _contextVars[varName];
                                    foreach (var filter in filters)
                                    {
                                        var filterType = typeof(Filters);
                                        var filterMethod = filterType.GetMethod(filter, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Static);
                                        
                                        if (filterMethod != null && filterMethod.ReturnType == typeof(string))
                                        {
                                            var parameters = filterMethod.GetParameters();
                                            if (parameters.Length == 1)
                                            {
                                                Type paramType = parameters[0].ParameterType;
                                                var typedValue = Convert.ChangeType(value, paramType);
                                                value = (string)filterMethod.Invoke(null, new[] { typedValue });
                                            }
                                        }
                                    }
                                }
                                else value = "";

                                switch (state)
                                {
                                    case State.Tag:
                                        xw.WriteString(value);
                                        for (; openTags > 0; --openTags) xw.WriteEndElement();
                                        break;
                                    case State.Value:
                                        xw.WriteString(value);
                                        state = State.Attribute;
                                        break;
                                    case State.Attribute:
                                        xw.WriteStartAttribute(SafeAttribute(value));
                                        state = State.Value;
                                        break;
                                }
                                break;
                            case '/':
                                break;
                            case '(':
                                switch (state)
                                {
                                    case State.Tag:
                                        if (openTags > 0)
                                            state = State.Attribute;
                                        break;
                                }
                                break;
                            case ')':
                                switch (state)
                                {
                                    case State.Attribute:
                                        state = State.Tag;
                                        break;
                                }
                                break;
                            case '=':
                                state = State.Value;
                                break;
                            case '{':
                                stack.Push(openTags);
                                openTags = 0;
                                break;
                            case '}':
                                for (; openTags > 0; --openTags) xw.WriteEndElement();
                                openTags = stack.Count > 0 ? stack.Pop() : 0;
                                for (; openTags > 0; --openTags) xw.WriteEndElement();
                                break;
                            case '"':
                            case '\'':
                                char q = token[0];
                                int n = (token.Length >= 6 && token[1] == q && token[2] == q && token.Get(-2) == q && token.Get(-3) == q ? 3 : 1);
                                string str = token.Slice(n, -n).Replace("\\'", "'").Replace("\\\"", "\"");
                                
                                switch (state)
                                {
                                    case State.Tag:
                                        for (; openAttrs > 0; --openAttrs) xw.WriteEndAttribute();
                                        xw.WriteString(str);
                                        for (; openTags > 0; --openTags) xw.WriteEndElement();
                                        break;
                                    case State.Value:
                                        xw.WriteString(str);
                                        state = State.Attribute;
                                        for (; openAttrs > 0; --openAttrs) xw.WriteEndAttribute();
                                        break;
                                    case State.Attribute:
                                        xw.WriteStartAttribute(SafeAttribute(str));
                                        ++openAttrs;
                                        break;
                                }
                                
                                break;
                            case ';':
                                switch (state)
                                {
                                    case State.Tag:
                                        for (; openTags > 0; --openTags) xw.WriteEndElement();
                                        break;
                                }
                                break;
                            default:
                                switch (state)
                                {
                                    case State.Attribute:
                                        xw.WriteStartAttribute(token);
                                        ++openAttrs;
                                        break;
                                    case State.Tag:
                                        xw.WriteStartElement(token);
                                        ++openTags;
                                        lastTag = token;
                                        break;
                                    case State.Value:
                                        xw.WriteString(token);
                                        state = State.Attribute;
                                        for (; openAttrs > 0; --openAttrs) xw.WriteEndAttribute();
                                        break;
                                }
                                break;
                        }
                        
                    }
                }
                output.Text = sw.ToString();
            }
        }
    }
}
